"""
author: Maker Sun
http://suniot.top
2024.9
"""
import network
import urequests
import os
import machine
import time
import json
import sys
import ntptime
from microdot import Microdot, Response, send_file
from machine import Pin, I2C, RTC, Timer
from pcf8563 import PCF8563
from micropython import const
from objs import FeedSetting, Options

#设置变量
base_url = const('http://iot.suniot.top/api')


access_token = None

button_time = None #按钮按下的时间
button_pin = None #按钮管脚

led_pin = None #led
led_frequency = 0 #led灯闪烁的频率 ms
led_timer = None #led定时器

device_key = None #设备的key
device_token = None #设备登录后的token

time_is_ok = False #标志系统时间是否OK

opts:Options = Options()



def read_ap_info():
    print('读取wifi配置文件')
    if 'wifi_info.txt' in os.listdir():
        print('发现wifi配置文件')
        with open('wifi_info.txt', 'r') as f:
            ssid = f.readline().strip()
            pwd = f.readline().strip()
            print('读取wifi配置文件成功',ssid, pwd)
            return ssid,pwd
        
    print('wifi配置文件不存在')
    return None, None

#扫描附近的WiFi热点，返回一个热点名称和是否完全开放（不需要密码）
def scan_wifi():
    #扫描附近的热点，返回热点列表
    print('扫描附近的WiFi热点...')
    import network #有时候外面的import不起作用
    
    sta_if = network.WLAN(network.STA_IF) #创建个WLAN的实例
    sta_if.active(False)#先关闭，再打开
    if not sta_if.isconnected():
        sta_if.active(True)  #激活网络接口
    networks = sta_if.scan() #扫描可用的无线网络。networks是扫描结果的元组数组
    ssids = []
    for network in networks: #循环，打印热点信息
        ssid = network[0].decode('utf-8')
        is_open = network[4]==0 #是否是开放热点，不需要密码
        if len(ssid)>0:
            ssids.append({"ssid":ssid, "is_open":is_open}) #每个热点一个元组，ssid，是否需要密码
    sta_if.active(False)
    print('扫描WiFi热点完成，发现',len(ssids),'个热点')
    return ssids

#长按重置按钮后，删除WiFi热点信息
def reset_ap_info():
    global opts
    opts.ssid = None
    opts.pwd = None
    opts.save()
    if True:
        return
    if 'wifi_info.txt' in os.listdir():
        print('文件已删除')
        os.remove('wifi_info.txt')

#将热点信息写入文件
def write_ap_info(ssid, pwd):
    #保存热点信息
    global opts
    opts.ssid=ssid
    opts.pwd = pwd
    opts.save()
    if True:
        return 
    with open('wifi_info.txt', 'w') as f:
        f.write(ssid + '\n')
        f.write(pwd + '\n')
    print('写入wifi配置文件成功...')

# 重启设备，默认3秒后重启
def restart(sec=3):
    # 设置定时器，3秒后执行restart_device函数
    print(sec,'秒后重启')
    timer = machine.Timer(-1) #创建虚拟Timer
    timer.init(period=sec*1000, mode=machine.Timer.ONE_SHOT, callback=lambda t:machine.reset())

#启动AP配置服务
def start_config_server():
    wifi_list = scan_wifi() #获取热点列表
    #启动WiFi热点
    print('正在启动wifi热点：ESP32-WiFi')
    ap = network.WLAN(network.AP_IF)
    ap.active(True)
    ap.config(ssid='ESP32-WiFi')
    print('wifi热点：ESP32-WiFi 已启动...')
    #启动配置服务，提供一个页面进行
    print('启动服务，连接到热点后，用浏览器打开地址：http://192.168.4.1/ 进行配置')
    app = Microdot()# 创建Web服务
    
    @app.route('/')  #绑定配置页面路由
    def index_page(request):
        print('/ 配置页面请求，返回index.html')
        return send_file('index.html')
    
    @app.route('/list')  #绑定配置页面路由
    def get_ap_list(request):
        print('/list 请求，返回', wifi_list)
        return Response(body=wifi_list)

    @app.route('/save', methods=['POST']) #绑定提交页面路由
    def save_method(request):
        data = request.json
        print('/save请求，数据：',data)
        ssid = data.get("ssid")
        pwd = data.get("pwd")
        if ssid==None or ssid=='':
            return Response(body={"ret":False, "msg":"ssid不能为空！"})
        else:
            write_ap_info(ssid, pwd)
            restart()
            return Response(body={"ret":True, "msg":"保存成功，三秒后重启！"})
    
    # 启动Web服务器
    print("服务已经启动，等待连接....")
    app.run(host='0.0.0.0', port=80)

#连接到WiFi热点    
def connect_wifi(ssid, pwd):
    try:
        print('尝试连接到WiFi'+ssid)
        wlan = network.WLAN(network.STA_IF) # 创建站点接口
        wlan.active(False)
        wlan.active(True)                   # 激活接口
        if not wlan.isconnected():          # 检查是否连接 
            wlan.connect(ssid, pwd)    # 连接到热点
            i = 0
            while not wlan.isconnected():   # 检查状态直到连接到热点或失败
                time.sleep(1) #等待1秒
                print('正在连接',ssid,i)
                i = i+1
                if i>=5: #大于5秒超时，返回
                    print('连接网络尝试大于5次')
                    return False
        print('已经连接到WiFi：', ssid)        
        print('网络信息:', wlan.ifconfig()) #打印网络信息
        return True
    except Exception as e:
            #失败，进行重试
        print('连接网络失败', e)
        return False

def setup_network():
    #连接热点，如果没有设置过网络，则进入配网模式
    #ssid,pwd = read_ap_info()
    global opts
    ssid = opts.ssid
    pwd = opts.pwd
    if ssid and len(ssid)>0:
        return connect_wifi(ssid, pwd)
    else:
        led_blink(0.25)#led快速闪烁，进入网络配置状态
        start_config_server()
    return False
#读取设备Key，返回key字符串，用于与服务器通信时验证身份
def read_device_key():
    if 'key.txt' in os.listdir():
        with open('key.txt', 'r') as f:
            key = f.readline().strip()
            return key
        
    print('key不存在')
    return None

def led_blink(t=0):
    #t<0，熄灭
    global led_timer
    global led_pin
    
    def blink(led):
        if led.value()==0:
            led.value(1)
        else:
            led.value(0)
    if t>=0:
        if led_timer==None:
            led_timer = Timer(0)
        led_timer.deinit()
        if t==0:
            led_pin.value(0)
        else:
            led_timer.init(period=int(t*1000), mode=Timer.PERIODIC, callback=lambda t:blink(led_pin))
    else:
        if led_timer!=None:
            led_timer.deinit() 
    
def http_post(path, headers={}, data={'hello':'hello'}):

#    response = None
#    try:

        # 发送POST请求
    headers['Content-Type'] = 'application/json'
    url = (base_url if base_url else '') +path
    print('请求：', url, headers, data)
    response = urequests.post(url=url, json=data, headers=headers)
    #print(response)
    # 检查响应状态码  
    #response. .raise_for_status()  # 如果状态码不是200，将抛出HTTPError  
    # 将响应的JSON字符串解码为Python字典  
    response_data = response.json()
    
    response.close() 
    # 返回解码后的数据  
    return response_data
'''    except Exception as e:  
        print(f"An error occurred: {e}")  
        return {'code': -999, 'msg': f'请求失败{e}'}  
    finally:  
        # 关闭响应对象以释放资源
        if response:
            response.close()  
'''

def verify_device():
    #key = read_device_key()
    global opts
    if opts.device_key:
        data = http_post(path='/device/verify', headers={},  data={'key': opts.device_key})
        print('verify device return object:', data)
        if data.get('code')==0:
            global access_token
            access_token = data.get('token')
            return True
    return False

def api_post(path, data):
    #API调用的POST请求，自动进行设备的验证
    global access_token
    if not access_token:
        verify_device()
    if access_token:
        headers={'token': access_token}
        ret = http_post(path=path, headers=headers, data=data)
        print('api post return :', ret)
        if ret.get('code') in [300, 340]:  #对于会话的等情况，进行一次尝试
            print('会话失效重新请求....')
            if verify_device(): #尝试
                ret = http_post(path=path, headers=headers, data=data)
        return ret
    else:
        return {'code':-1, 'msg': '验证设备失败'}

def get_server_rtc_time():
    ret = http_post(path='/iot/time')
    if ret.get('code')==0:
        dt = ret.get('time')
        if dt and len(dt)==8:
            return dt
    return None

def sync_ntptime(pcf8563: PCF8653):
    #同步时间
    print("同步前本地时间：%s" %str(time.localtime()))
    for i in range(3): #尝试3次
        try:
            #ntptime.settime()
            #同步rtc时钟
            dt = get_server_rtc_time()
            if dt:
                rtc = RTC()
                rtc.datetime(tuple(dt))
                pcf8563.datetime(dt) #设置网络时间
                pcf8563.timer_enable(False) #禁用倒计时
                pcf8563.timer_frequency(pcf8563.TIMER_FREQUENCY_1_64) #设置到最低频率
                pcf8563.timer_timeout(255) #用倒计时的这个来标志下已经同步过网络时间
                print('设置标志成功', pcf8563.timer_timeout())
                print('同步网络时间成功', time.localtime())
                return True #成功，返回True
        except Exception as e:
            #失败，进行重试
            print('同步时间失败重试', i, e)
    return False #没有同步成功返回False

def sync_rtctime(pcf8563: PCF8653):
    try:
        print("RTC设置标志：", pcf8563.timer_timeout())
        if pcf8563.timer_timeout()==255:
            dt = pcf8563.datetime()
            rtc = RTC()
            rtc.datetime(dt)
            print('从rtc同步时间成功', time.localtime())
    except Exception as e:
        print('从rtc同步时间失败', e)

def send_message(action, data):
    #发送指令
    api_post(path='/iot/sendmessage', data={'action': action, 'data': data})


def get_last_message(action):
    #获取最新的消息
    ret = api_post(path='/iot/getlastmessage', data={'action': action})
    print('get last messsage', ret)
    if ret.get('code')==0:
        return ret
    return None

def do_feeding(apin: Pin, bpin: Pin, sound_pin: Pin, feeding_set: int):
    #执行喂食
    sound_pin.value(1)
    time.sleep(2)
    apin.value(1)
    time.sleep(feeding_set*3)
    apin.value(0)
    sound_pin.value(0)


#按钮中断处理函数
def button_irq_handler(pin: Pin):
    #按下5秒重置
    print('irq')
    import time
    global button_time
    if pin.value() == 0:
        button_time = time.ticks_ms()
    else:
        if button_time!=None:
            time = time.ticks_diff(time.ticks_ms(), button_time)
            if time >= 5000: #按的时间超过5秒后，进行重置
                reset_ap_info(); #重置ap连接信息
                restart(); #重启设备

def ir_handler(pin):
    #粮食缺乏时触发
    if pin.value()==1:
        pass #缺粮
    else:
        pass #不缺粮

def rtc_int_handler(pin, pcf8563):
    #RTC模块中断
    #出粮，并设置下次出粮的中断
    print("*** RTC Interrupt ***", pin.value())
    if pcf8563.timer_flag():
        pcf8563.timer_flag(True)
        
    #rtc.print_timer_state()
    #rtc.print_alarm_state()
    #if rtc.timer_flag():
    #    rtc.timer_flag(True)


def main():
    #初始化端口
    #重置按钮
    global button_pin
    global opts
    opts.load()
    
    #重置按钮
    button_pin = Pin(12, Pin.IN, Pin.PULL_UP)
    button_pin.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=button_irq_handler)
    #红外对射，缺粮检测
    #ir_pin = Pin(14, Pin.IN, Pin.PULL_DOWN)
    #ir_pin.irq(trigger=Pin.IRQ_FALLING , handler=ir_handler)
    ir_pin = Pin(14, Pin.IN)
    #声音播放控制
    sound_pin = Pin(16, Pin.OUT, Pin.PULL_DOWN)
    #出粮机构马达驱动
    moto_a_pin = Pin(13, Pin.OUT, Pin.PULL_DOWN)
    moto_b_pin = Pin(15, Pin.OUT, Pin.PULL_DOWN)
    #LED指示灯
    global led_pin, led_timer
    led_pin = Pin(18, Pin.OUT, Pin.PULL_UP)
    led_timer = Timer(0)
    #RTC时钟模块
    i2c = I2C(1)
    #RTC定时器中断
    pcf8563 = PCF8563(i2c)
    rtc_int_pin = Pin(27, Pin.IN, Pin.PULL_DOWN)
    #rtc_int_pin.irq(trigger=Pin.IRQ_RISING, handler=lambda t:rtc_int_handler(rtc_int_pin, pcf8563))
    
    #连接网络
    setup_network()

    #同步时间
    global time_is_ok
    if sync_ntptime(pcf8563): #同步网络时间
        time_is_ok = True
    else:
        if sync_rtctime(pcf8563): #同步RTC时间
            time_is_ok = True
    
    #设置喂食定时器
    feed_time = None
    feed_set = None
    last_time = None #最近一次出粮时间
    feed_id = None
    while True:
        #赶时间，下面的代码写的有点潦草
        if feed_time == None:
            setting = opts.get_next_feed_time()
            if setting:
                feed_time = setting.time
                feed_set = setting.set
        if feed_time:
            t = time.localtime()
            cur_time = f"{t[3]:0>{2}}:{t[4]:0>{2}}"
            print('当前时间:', cur_time, '出粮时间:', feed_time)
            if last_time==None and feed_time==cur_time:
                last_time = cur_time
                do_feeding(moto_a_pin, moto_b_pin, sound_pin, feed_set)
                try:
                    send_message('feeding', {'time':cur_time, 'set': feed_set})
                except Exception as e:
                    print('send message failed :', e)
                    pass
                feed_time = None
                feed_set = None
            if last_time!=None and last_time!=cur_time:
                last_time = None  
        try:
            data = get_last_message(action='setting')
            if data:
                fd_id = data.get('id')
                if fd_id != opts.feed_setting_msg_id:
                    print('更新本地setting', fd_id)
                    opts.load_feed_settings(data.get('data'))
                    opts.feed_setting_msg_id = fd_id
                    opts.save()
                    feed_time=None
            data = get_last_message(action='feed')
            if data:
                fd_id = data.get('id')
                fd_time = data.get('time')
                time_minute = 0
                if fd_time != None and len(fd_time)==19:
                    time_minute = int(fd_time[11:13])*60+int(fd_time[14:16])
                if time_minute>0 and feed_id != fd_id:
                    feed_id = fd_id
                    t = time.localtime()
                    cur_time = f"{t[3]:0>{2}}:{t[4]:0>{2}}"
                    cur_time_minute = t[3]*60+t[4]
                    if abs(cur_time_minute-time_minute)<1:
                        feed_set = data.get('data').get('set')
                        print('立即出粮', fd_id)
                        do_feeding(moto_a_pin, moto_b_pin, sound_pin, feed_set)
                        try:
                            send_message('feeding', {'time':cur_time, 'set': feed_set})
                        except Exception as e:
                            print('send message failed :', e)
            #缺粮判断
            data = get_last_message(action='foodstate')
            if data and data.get('data'):
                empty = ir_pin.value()==0
                last_empty = data['data'].get('empty')
                if empty!=last_empty:
                    send_message(action='foodstate', data={'empty': empty})
        except Exception as e:
            print('执行有错误',e)
        
        #
        
            
        time.sleep(5)
#main()
#测试用Timer控制Led的闪烁  
def test_led_blink():
    global led_pin
    led_pin = Pin(18, Pin.OUT, Pin.PULL_UP)
    global led_timer
    led_timer = Timer(0)
    
    i = 0.5
    while True:
        time.sleep(5)
        print("test led blink ", i)
        i = i + 0.5
        if i == 2:
            i = 0
        led_blink(i)

def test_reset_button():
    global button_pin
    button_pin = Pin(12, Pin.IN, Pin.PULL_UP)
    button_pin.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=button_irq_handler)
    while True:
        time.sleep(3)

def test_pcf8653():
    alarm_int_pin = Pin(27, Pin.IN, Pin.PULL_UP)
    
    # now = time.localtime()
    # print(now)
    #(year, month, day, hour, minute, second, weekday, msec) = now
    i2c = I2C(1)
    pcf8563 = PCF8563(i2c)
    alarm_int_pin.irq(trigger=Pin.IRQ_FALLING , handler=lambda t:rtc_int_handler(alarm_int_pin, rtc))
    #rtc.datetime([year, month, day, weekday, hour, minute, second])
    print("datetime:", pcf8563.datetime())
    #rtc.alarm_day(31)
    #rtc.alarm_hour(23)
    #rtc.alarm_minute(40)
    #rtc.alarm_week(3)
    #rtc.timer_timout(10)
    
    #print("alarm week, day, hour, minute:", rtc.alarm_week(), rtc.alarm_day(), rtc.alarm_hour(), rtc.alarm_minute())
    #print("alarm array[week, day, hour, minute]:", rtc.alarm())
    #print("alarm flag(AF):", rtc.alarm_flag())
    
    pcf8563.timer_timeout(5)
    pcf8563.timer_frequency(PCF8563.TIMER_FREQUENCY_1)
    pcf8563.timer_enable(True)
    pcf8563.timer_flag(True)
    pcf8563.ti_tp_enable(False)
    pcf8563.timer_interupt(True)
    pcf8563.print_timer_state()
    return pcf8563
''' 
    pcf8563.alarm_week(-1)
    pcf8563.alarm_day(-1)
    pcf8563.alarm_hour(-1)
    pcf8563.alarm_day(6)
    pcf8563.alarm_hour(19)
    pcf8563.alarm_minute(45)
    pcf8563.alarm_flag(True)
    pcf8563.ti_tp_enable(True)
    pcf8563.alarm_interupt(True)
    pcf8563.print_alarm_state()
'''  
    
def test_time():
    print(time.localtime())
    
    #rtc.year(2024)
    #print(rtc.datetime())
    #rtc.month(9)
    #print(rtc.datetime())
    #i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000)
#set_ntptime()
#test_led_blink()
    
def test_moto():
    moto_a_pin = Pin(13, Pin.OUT, Pin.PULL_DOWN)
    moto_b_pin = Pin(15, Pin.OUT, Pin.PULL_DOWN)
    moto_a_pin.value(1)
    time.sleep(1)
    moto_a_pin.value(0)
    

#test_moto()

#opts.load()
main()
'''
setup_network()
rtc = test_pcf8653()
#rtc.print_reg()

t = 0
while True:
    time.sleep(5)
    t+=5
    print(t, '------')
    rtc.print_timer_state()
    #rtc.print_alarm_state()
'''
#test_reset_button()
#test_time()
